package plugin.bpce.Object;

import java.io.BufferedWriter;
import java.io.File;
import java.io.FileWriter;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.xpath.XPath;
import javax.xml.xpath.XPathConstants;
import javax.xml.xpath.XPathFactory;

import org.w3c.dom.DOMImplementation;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;

import plugin.bpce.modelo.ConfiguracaoModelo;
import plugin.bpce.modelo.DiagramaPublicado;
import plugin.bpce.modelo.ModeloAvaliado;

import com.sun.org.apache.xml.internal.serialize.OutputFormat;
import com.sun.org.apache.xml.internal.serialize.XMLSerializer;

public class RepositoryObject {
	private String path, nomeUsuario;
	private DocumentBuilder build;
	private DocumentBuilderFactory factory;
	private XPath xpath;
	private Document docRO;
	private Element noRaiz;
	private Node noModeladores;
	private DOMImplementation dom;
	private ConfiguracaoModelo configuracao;

	public RepositoryObject(String coppeerHome, String nomeUsuario, ConfiguracaoModelo configuracao) {
		File tmp;		
		
		tmp = new File(coppeerHome + "/rep_BPCE/");
		if (!(tmp.exists()))
			tmp.mkdirs();
		
		this.path = coppeerHome + "/rep_BPCE/RO.xml";		
		this.nomeUsuario = nomeUsuario;
		this.configuracao = configuracao;
		tmp = new File(this.path);
		
		try{
		    factory = DocumentBuilderFactory.newInstance();
	        factory.setNamespaceAware(true);	         
	        build = factory.newDocumentBuilder();
	        xpath = XPathFactory.newInstance().newXPath();
	        
			this.docRO = this.build.parse(tmp);			
			this.noRaiz = this.docRO.getDocumentElement();
			
			this.noModeladores = (Node) xpath.evaluate("//RepositoryObject/Modeladores", this.docRO, XPathConstants.NODE);
		}
		catch(Exception e){
			geraArquivo();			
		}		
	}

	private void geraArquivo() {
		Element noModelador;
		
		try{
			this.dom = this.build.getDOMImplementation();
			this.docRO = this.dom.createDocument(null, null, null);
			
			noRaiz = this.docRO.createElement("RepositoryObject");						
			
			noModeladores = this.docRO.createElement("Modeladores");
			noModelador = this.docRO.createElement("Modelador");			
			noModelador.setAttribute("Nome", nomeUsuario);
			noModeladores.appendChild(noModelador);
			
			noRaiz.appendChild(noModeladores);
			
			this.docRO.insertBefore(noRaiz, null);
			
			geraXML();
		}
		catch(Exception e){
			e.printStackTrace();
		}		
	}

	private void geraXML() {
        FileWriter writer;
        BufferedWriter buf;
        OutputFormat format;
        XMLSerializer output;
        
        try{
            writer = new FileWriter(this.path);
            buf = new BufferedWriter(writer);            
            format = new OutputFormat(this.docRO, "ISO-8859-1", true);
            format.setLineSeparator(System.getProperty("line.separator"));
            output = new XMLSerializer(buf, format);
            output.serialize(this.docRO);
        } catch(Exception e){
            e.printStackTrace();
        }			
	}

	public void publicaModelo(String nomeDiagrama, String nomeAutor, boolean avaliou, 
			String nomeAgente, List<ModeloAvaliado> listaModeloAvaliado) {
		Node noModelador;
		float ptoInicial;
		
		noModelador = getNoModelador(nomeAutor);
		
		if (noModelador == null)
			noModelador = addModelador(nomeAutor);
		
		ptoInicial = calculaPontuacaoInicial(listaModeloAvaliado);
		
		addDiagramaModelador(noModelador, nomeDiagrama, ptoInicial, 0, 0, avaliou, nomeAgente);
		
		geraXML();
	}

	private float calculaPontuacaoInicial(List<ModeloAvaliado> listaModeloAvaliado) {
		float ptoInicial, temp, total;
		ModeloAvaliado modelo;
		int i;		
		
		ptoInicial = Float.parseFloat(configuracao.getPontuacaoInicialDiagrama());
		total = 0;
		
		for(i = 0; i < listaModeloAvaliado.size(); i++){
			modelo = listaModeloAvaliado.get(i);			
			temp = getPontuacaoDiagrama(modelo.getNomeModelo(), modelo.getNomeAutor());
			temp = temp * modelo.getPontuacao() / 3;
			total += temp;
			
			pontuaModelo(modelo.getNomeModelo(), modelo.getNomeAutor(), modelo.getNomePontuador(), modelo.getPontuacao(), 1);
		}
		
		if (i > 0){
			total = total / i;
			ptoInicial += total;
		}
		
		return ptoInicial;
	}

	private void addDiagramaModelador(Node noModelador, String nomeDiagrama, float ptoInicial, float ptoVisualizacao, 
			float ptoAvaliacao, boolean avaliou, String nomeAgente) {
		Element noDiagrama;
				
		noDiagrama = this.docRO.createElement("Diagrama");		
		noDiagrama.setAttribute("PtoInicial", Float.toString(ptoInicial));		
		noDiagrama.setAttribute("PtoAvaliacao", Float.toString(ptoAvaliacao));
		noDiagrama.setAttribute("PtoVisualizacao", Float.toString(ptoVisualizacao));
		noDiagrama.setAttribute("Agente", nomeAgente);
		noDiagrama.setAttribute("NomeDiagrama", nomeDiagrama);
		
		if (avaliou)
			noDiagrama.setAttribute("Avaliou", "1");
		else
			noDiagrama.setAttribute("Avaliou", "0");
		
		noModelador.appendChild(noDiagrama);
	}

	private Node addModelador(String nomeAutor) {
		Element noModelador;
		
		noModelador = this.docRO.createElement("Modelador");
		noModelador.setAttribute("Nome", nomeAutor);
		
		this.noModeladores.appendChild(noModelador);
		
		return noModelador;
	}

	public String getReputacaoModelador(String nomeAutor) {
		float ptoModelador, repModelador, ptoTotalModeladores;
		String retorno;
		
		ptoModelador = calculaPontuacaoModelador(nomeAutor);
		ptoTotalModeladores = getPontuacaoTotalModeladores();
		repModelador = ptoModelador / ptoTotalModeladores;
		
		retorno = Float.toString(repModelador);		
		return retorno;
	}

	private float getPontuacaoTotalModeladores() {
		NodeList nlModeladores;
		int i;
		Element tmp;
		float totPontuacaoModeladores;
		
		totPontuacaoModeladores = 0;
		
		nlModeladores = getNoModeladores();
			
		for(i = 0; i < nlModeladores.getLength(); i++){
			tmp = (Element)nlModeladores.item(i);
			totPontuacaoModeladores += calculaPontuacaoModelador(tmp.getAttribute("Nome"));
		}
		
		return totPontuacaoModeladores;
	}
	
	private NodeList getNoModeladores(){
		NodeList nlModeladores;
		
		try{
			nlModeladores = (NodeList) xpath.evaluate("//RepositoryObject/Modeladores/Modelador", this.noRaiz, XPathConstants.NODESET);			
		}
		catch(Exception e){
			nlModeladores = null;
		}
		
		return nlModeladores;
	}

	public float calculaPontuacaoModelador(String nomeAutor) {		
		float pontoTotal, ptoTmp, contaAvaliacoes, pontuacaoModelador, tmpInicial, tmpVisualizacao,
			tmpAvaliacao;
		int i;
		Element noDiagrama;
		NodeList nlDiagramas;
		String avaliou;
		
		pontoTotal = 0;
		contaAvaliacoes = 0;		
		pontuacaoModelador = 0;		
		nlDiagramas = getDiagramasModelador(nomeAutor);
		
		for(i = 0; i < nlDiagramas.getLength(); i++){
			noDiagrama = (Element) nlDiagramas.item(i);
			
			tmpInicial = Float.parseFloat(noDiagrama.getAttribute("PtoInicial"));
			tmpVisualizacao = Float.parseFloat(noDiagrama.getAttribute("PtoVisualizacao"));
			tmpAvaliacao = Float.parseFloat(noDiagrama.getAttribute("PtoAvaliacao"));

			ptoTmp = (tmpInicial * Float.parseFloat(this.configuracao.getPesoPontuacaoInicial())) + 
					 (tmpVisualizacao * Float.parseFloat(this.configuracao.getPesoPontuacaoVisualizacao())) + 
					 (tmpAvaliacao * Float.parseFloat(this.configuracao.getPesoPontuacaoAvaliacao()));
			
			pontoTotal += ptoTmp;
			
			avaliou = noDiagrama.getAttribute("Avaliou");
			if (avaliou.equalsIgnoreCase("1")){
				contaAvaliacoes++;
			}
		}
		
		pontuacaoModelador = (pontoTotal / (i)) + contaAvaliacoes;
		
		return pontuacaoModelador;
	}
	
	public float getPontuacaoDiagrama(String nomeDiagrama, String nomeAutor) {
		float result = 0;
		Element tmp;
		
		try{
			tmp = getNoDiagrama(nomeDiagrama, nomeAutor);
		
			result = Float.parseFloat(tmp.getAttribute("PtoInicial")) * Float.parseFloat(this.configuracao.getPesoPontuacaoInicial());
			result += Float.parseFloat(tmp.getAttribute("PtoVisualizacao")) * Float.parseFloat(this.configuracao.getPesoPontuacaoVisualizacao());
			result += Float.parseFloat(tmp.getAttribute("PtoAvaliacao")) * Float.parseFloat(this.configuracao.getPesoPontuacaoAvaliacao());
		}
		catch(Exception e){
			e.printStackTrace();
		}
		
		return result;
	}

	public void pontuaModelo(String nomeModelo, String nomeAutor, String nomePontuador, int indice, int tipoPontuacao) {		
		Element noDiagrama;
		float pto, repPontuador, ptoRecebido;
	
		noDiagrama = getNoDiagrama(nomeModelo, nomeAutor);
		repPontuador = Float.parseFloat(getReputacaoModelador(nomePontuador));
		ptoRecebido = repPontuador * indice;
		
		if (tipoPontuacao == 0){
			pto = Float.parseFloat(noDiagrama.getAttribute("PtoVisualizacao"));
			pto += ptoRecebido;
			noDiagrama.setAttribute("PtoVisualizacao", Float.toString(pto));
		}
		else{
			pto = Float.parseFloat(noDiagrama.getAttribute("PtoAvaliacao"));
			pto += ptoRecebido;
			noDiagrama.setAttribute("PtoAvaliacao", Float.toString(pto));
		}
		
		geraXML();
	}
	
	private Element getNoDiagrama(String nomeModelo, String nomeAutor){
		Element no;
		
		try{
			no = (Element) xpath.evaluate("//RepositoryObject/Modeladores/Modelador[@Nome='" + nomeAutor + "']/Diagrama[@NomeDiagrama='" + nomeModelo + "']", this.noRaiz, XPathConstants.NODE);
		}
		catch(Exception e){
			no = null;
		}
		
		return no;
	}
	
	private Element getNoModelador(String nomeModelador){
		Element no;
		
		try{
			no = (Element) xpath.evaluate("//RepositoryObject/Modeladores/Modelador[@Nome='" + nomeModelador + "']", this.docRO, XPathConstants.NODE);
		}
		catch(Exception e){
			e.printStackTrace();
			no = null;
		}
		
		return no;
	}

	public void setConfiguracao(ConfiguracaoModelo configuracao) {
		this.configuracao = configuracao;
	}

	public List<String> getModeladores() {
		NodeList nlModeladores;
		int i;
		Element noModelador;
		List<String> retorno;
		
		nlModeladores = getNoModeladores();
		retorno = new ArrayList<String>();
		
		for(i = 0; i < nlModeladores.getLength(); i++){
			noModelador = (Element) nlModeladores.item(i);
			retorno.add(noModelador.getAttribute("Nome"));
		}
		
		return retorno;
	}

	public Map<String, List<DiagramaPublicado>> getDiagramas(){
		Map<String, List<DiagramaPublicado>> retorno;
		List<String> modeladores;
		NodeList nlDiagramas;
		int i, j;
		String nomeModelador;
		Element noDiagrama;
		List<DiagramaPublicado> listaDiagramas;
		DiagramaPublicado diagrama;
		
		retorno = new HashMap<String, List<DiagramaPublicado>>();
		modeladores = getModeladores();		
		
		for(i = 0; i < modeladores.size(); i++){
			nomeModelador = modeladores.get(i);
			nlDiagramas = getDiagramasModelador(nomeModelador);
			listaDiagramas = new ArrayList<DiagramaPublicado>();
			
			for(j = 0; j < nlDiagramas.getLength(); j++){
				noDiagrama = (Element) nlDiagramas.item(j);
				diagrama = new DiagramaPublicado();
				diagrama.setAgente(noDiagrama.getAttribute("Agente"));
				diagrama.setAvaliou(noDiagrama.getAttribute("Avaliou"));
				diagrama.setNomeDiagrama(noDiagrama.getAttribute("NomeDiagrama"));
				diagrama.setPtoAvaliacao(noDiagrama.getAttribute("PtoAvaliacao"));
				diagrama.setPtoInicial(noDiagrama.getAttribute("PtoInicial"));
				diagrama.setPtoVisualizacao(noDiagrama.getAttribute("PtoVisualizacao"));
				
				listaDiagramas.add(diagrama);
			}
			
			retorno.put(nomeModelador, listaDiagramas);
		}
		
		return retorno;
	}

	private NodeList getDiagramasModelador(String nomeModelador) {
		NodeList nlDiagramas;
		
		try{
			nlDiagramas = (NodeList) xpath.evaluate("//RepositoryObject/Modeladores/Modelador[@Nome='" + nomeModelador + "']/Diagrama", this.noRaiz, XPathConstants.NODESET);
		}
		catch(Exception e){
			nlDiagramas = null;
		}
		
		return nlDiagramas;
	}

	public void mergePontuacaoModelos(List<String> listaModeladoresRecebido, Map<String, List<DiagramaPublicado>> listaDiagramasRecebido) {
		List<String> listaModeladores;
		List<DiagramaPublicado> listaDiagramaModelador;
		int i, j;
		String nomeModelador;
		Node noModelador;
		DiagramaPublicado diagrama;
		boolean avaliou;
		
		listaModeladores = getModeladores();
		
		for(i = 0; i < listaModeladoresRecebido.size(); i++){
			nomeModelador = listaModeladoresRecebido.get(i);
			if (listaModeladores.contains(nomeModelador)){
				listaDiagramaModelador = listaDiagramasRecebido.get(nomeModelador);
				for(j = 0; j < listaDiagramaModelador.size(); j++){
					diagrama = listaDiagramaModelador.get(j);
					updateDiagramaModelador(nomeModelador, diagrama);
				}
			}
			else{
				listaDiagramaModelador = listaDiagramasRecebido.get(nomeModelador);
				noModelador = addModelador(nomeModelador);
				
				for(j = 0; j < listaDiagramaModelador.size(); j++){
					diagrama = listaDiagramaModelador.get(j);
					if (diagrama.getAvaliou().equalsIgnoreCase("1"))
						avaliou = true;
					else
						avaliou = false;
					
					addDiagramaModelador(noModelador, diagrama.getNomeDiagrama(), Float.parseFloat(diagrama.getPtoInicial()), Float.parseFloat(diagrama.getPtoVisualizacao()),
							Float.parseFloat(diagrama.getPtoAvaliacao()), avaliou, diagrama.getAgente());
				}
			}
		}
		
		geraXML();
	}

	private void updateDiagramaModelador(String nomeModelador, DiagramaPublicado diagrama) {
		Element noDiagrama;
		float ptoVisualizacao, ptoAvaliacao;
		boolean avaliou;
		
		noDiagrama = getNoDiagrama(diagrama.getNomeDiagrama(), nomeModelador);
		if (noDiagrama != null){
			ptoVisualizacao = Float.parseFloat(noDiagrama.getAttribute("PtoVisualizacao"));
			ptoAvaliacao = Float.parseFloat(noDiagrama.getAttribute("PtoAvaliacao"));
			
			if (Float.parseFloat(diagrama.getPtoVisualizacao()) > ptoVisualizacao)
				noDiagrama.setAttribute("PtoVisualizacao", diagrama.getPtoVisualizacao());
			
			if (Float.parseFloat(diagrama.getPtoAvaliacao()) > ptoAvaliacao)
				noDiagrama.setAttribute("PtoAvaliacao", diagrama.getPtoAvaliacao());
		}
		else{
			if(diagrama.getAvaliou().equalsIgnoreCase("1"))
				avaliou = true;
			else
				avaliou = false;
			
			addDiagramaModelador(getNoModelador(nomeModelador), diagrama.getNomeDiagrama(), 
					Float.parseFloat(diagrama.getPtoInicial()), Float.parseFloat(diagrama.getPtoVisualizacao()), 
					Float.parseFloat(diagrama.getPtoAvaliacao()), avaliou, diagrama.getAgente());
		}
	}
}